using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace RiverRace
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Define special vertex type.
        public struct VertexPositionColorNormal
        {
            public Vector3 Position;
            public Color Color;
            public Vector3 Normal;

            public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
            );
        }

        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        GraphicsDevice device;

        Effect effect;
        VertexPositionColorNormal[] vertices;
        //Matrix viewMatrix;
        //Matrix projectionMatrix;
        short[] indices;

        private float angle = 0f;

        Camera camera;

        //Variables that store information from height map to create vertices.
        Texture2D heightMap;
        private int terrainWidth;
        private int terrainHeight;
        private float[,] heightData;
        //Variable that stores color of terrain as an overlay.
        Texture2D colorMap;
        //Variable to scale the terrain when generated.
        Vector3 terrainScale = new Vector3(10, 5, 10);
        //Generate bounding boxes along river to keep player from entering land.
        //Create a 2D array of Colors to represent a picture
        Color[,] terrainColor;
        BoundingBox[] riverEdge;

        Vector3 boatPosition = new Vector3(20.0f, 0f, 1000);
        float boatRotation = 0.0f;
        float boatMaxSpeed = 1f;
        float boatAbsMaxSpeed = 4.0f;
        float boatSpeed = 0.0f;
        float boatAccel = 0.01f;
        float maxAngle = 30;
        float knockback = 1.5f; // Scales the amount of recoil when the boat hits a buoy (1 = perfectly elastic)

        bool sinking = false;
        int sinkTimerCount = 0;
        int sinkTimerDelay = 120;

        static float finishZ = -900.0f;
        Vector3 finishCamPos = new Vector3(0.0f, 10.0f, finishZ + 30.0f);
        bool passedFinish = false;

        bool testKnockbackPressed = false;

        // Width of river (not accounting for xScale)
        int riverWidth = 57;

        // List of buoy positions.
        List<Vector3> buoyPositions = new List<Vector3>();
        int numBuoys = 30;
        float bobOffset = 0.0f;
        float bobSpeed = 0.05f;
        float bobHeight = 1.5f;
        float waveSpeed = 4.0f;

        // Controls intensity of "swing" effect.
        float cameraSwing = 1.3f;

        //skybox variables
        Texture2D[] skyboxTextures;
        Model skyboxModel;
        Vector3 skyPosition = new Vector3(0, 0f, 100);

        //The font of our game
        SpriteFont aklatanic;

        //int to hold the score
        decimal iTime = 0;

        //iTime update delay
        decimal fTimeDelay = 1;
        decimal fTimeCounter = 0;

        //background for the timer
        Texture2D stopWatch;

        //Boolean for drawing bounding shapes.
        bool bDebugDrawing = false;

        // int to store what gamestate we are in
        // 0 = titlescreen
        // 1 = playing game
        int iGameState = 0;

        //Texture to hold the title screen
        Texture2D titleScreen;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            graphics.PreferredBackBufferWidth = 500;
            graphics.PreferredBackBufferHeight = 500;
            graphics.IsFullScreen = false;
            graphics.ApplyChanges();
            Window.Title = "River Race";

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            device = graphics.GraphicsDevice;

            effect = Content.Load<Effect>("Effects/effects");
            //Define cammera matrices
            SetUpCamera();


            //Load the height map.
            heightMap = Content.Load<Texture2D>("Textures/EarthMap");
            //Load the color map.
            colorMap = Content.Load<Texture2D>("Textures/EarthMap_c_Finish");

            //load the skybox mode
            skyboxModel = LoadModel("skybox2", out skyboxTextures);

            //Load the game font.
            aklatanic = Content.Load<SpriteFont>(@"Fonts\aklatanic");

            //Add in texture for the stopwatch background
            stopWatch = Content.Load<Texture2D>(@"Textures\healthBar");

            //load the background
            titleScreen = Content.Load<Texture2D>(@"Textures\TitleScreen1");

            //Load the height data into a 2D array
            LoadHeightData(heightMap);

            SetUpVertices();
            SetupColor();
            SetUpIndices();

            //Load height map into a 2D colorArray.
            terrainColor = MakeColorArray(heightMap);
            //Set up bounding boxes for river edge
            SetUpRiverEdge();

            //Calculate the normal data.
            CalculateNormals();

            // Generate a few buoys.
            generateBuoys();

            ModelHelper.LoadModels(Content);

            //Generate boat bounding box
            //boatBox = CalculateBoundingBox(ModelHelper.boatModel, new Vector3(0.02f, 0.02f, 0.02f));

            //Initialize a debugger for debugging bounding shapes.
            DebugShapeRenderer.Initialize(GraphicsDevice);
            
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        }

        private void generateBuoys()
        {
            // Generate a few buoys.
            Rectangle spawnRect = new Rectangle((int)(-20 * terrainScale.X),
                (int)(-100 * terrainScale.Z),
                (int)(30 * terrainScale.X),
                (int)(200 * terrainScale.Z));
            Random rng = new Random();
            for (int i = 0; i < numBuoys; i++)
            {
                buoyPositions.Add(new Vector3(rng.Next(spawnRect.Width) + spawnRect.X, -45,
                                              rng.Next(spawnRect.Height) + spawnRect.Y));
                ModelHelper.buoy_WorldMatrix.Add(Matrix.Identity);
            }

            ModelHelper.LoadModels(Content);
        }

        /**
         * SetUpVertices()
         * Create vertices of a terrain using a height map
         */
        private void SetUpVertices()
        {
            //Create vertices.
            vertices = new VertexPositionColorNormal[terrainWidth * terrainHeight];
            //Iterate through all vertices in the map.
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int z = 0; z < terrainHeight; z++)
                {
                    vertices[x + z * terrainWidth].Position = new Vector3(
                        x * terrainScale.X,
                        heightData[x, z] * terrainScale.Y,
                        -z * terrainScale.Z);
                    vertices[x + z * terrainWidth].Color = Color.White;
                }
            }
        }

        /**
         * Create bounding box of river edge based on height map colors.
         */
        private void SetUpRiverEdge()
        {
            //Create a bounding list.
            List<BoundingBox> riverList = new List<BoundingBox>();
            for (int z = 0; z < terrainHeight; z++)
            {
                //Go across width of river.
                for (int x = 0; x < terrainWidth; x++)
                {
                    int leftEdge = 0;
                    int offset = 0;
                    //Check if we have reached water
                    if (terrainColor[z, x].R == 0)
                    {
                        leftEdge = x;
                        //Figure out when we have reached right river edge
                        offset = 0;
                        while (terrainColor[z, x + offset].R == 0)
                        {
                            offset++;
                        }

                        //If we are done calculating the offset, we can generate a bounding box.
                        Vector3 leftMap = new Vector3(0, 0, z * terrainScale.Z);
                        Vector3 leftRiver = new Vector3(leftEdge * terrainScale.X, 40, (z + 1) * terrainScale.Z);

                        Vector3 rightRiver = new Vector3((leftEdge + offset) * terrainScale.X, 0, z * terrainScale.Z);
                        Vector3 rightMap = new Vector3(terrainWidth * terrainScale.X, 40, (z + 1) * terrainScale.Z);

                        //Apply translation on vectors.
                        Vector3 translationVector = new Vector3(-terrainWidth / 2.0f * terrainScale.X, 0, -terrainHeight / 2.0f * terrainScale.Z);
                        leftMap += translationVector;
                        leftRiver += translationVector;
                        rightRiver += translationVector;
                        rightMap += translationVector;

                        riverList.Add(new BoundingBox(leftMap, leftRiver));
                        //Right edge
                        riverList.Add(new BoundingBox(rightRiver, rightMap));
                        break;
                    }        
                }
            }
            riverEdge = riverList.ToArray();
        }

        private Model LoadModel(string assetName, out Texture2D[] textures)
        {

            Model newModel = Content.Load<Model>(assetName);
            textures = new Texture2D[newModel.Meshes.Count];
            int i = 0;
            foreach (ModelMesh mesh in newModel.Meshes)
                foreach (BasicEffect currentEffect in mesh.Effects)
                    textures[i++] = currentEffect.Texture;

            foreach (ModelMesh mesh in newModel.Meshes)
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    meshPart.Effect = effect.Clone();

            return newModel;
        }

        private void SetUpIndices()
        {
            indices = new short[(terrainWidth - 1) * (terrainHeight - 1) * 6];
            int counter = 0;
            for (int y = 0; y < terrainHeight - 1; y++)
            {
                for (int x = 0; x < terrainWidth - 1; x++)
                {
                    int lowerLeft = x + y * terrainWidth;
                    int lowerRight = (x + 1) + y * terrainWidth;
                    int topLeft = x + (y + 1) * terrainWidth;
                    int topRight = (x + 1) + (y + 1) * terrainWidth;

                    indices[counter++] = (short)topLeft;
                    indices[counter++] = (short)lowerRight;
                    indices[counter++] = (short)lowerLeft;

                    indices[counter++] = (short)topLeft;
                    indices[counter++] = (short)topRight;
                    indices[counter++] = (short)lowerRight;
                }
            }
        }

        /**
         * SetupColor() Iterate through all vertices and apply a color to them.
         */
        private void SetupColor()
        {
            //Load the color map into an array to process.
            Color[] colorMapColors = new Color[terrainWidth * terrainHeight];
            colorMap.GetData(colorMapColors);

            //Iterate through all pixels and set the colors.
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int z = 0; z < terrainHeight; z++)
                {
                    vertices[x * terrainWidth + z].Color = colorMapColors[x * terrainWidth + z];
                }
            }
        }

        /**
         * LoadHeightData(Texture2D heightMap)
         * Load the color of a height map into a 2d array which can
         * store the vertical height of a set of coordinates.
         */
        private void LoadHeightData(Texture2D heightMap)
        {
            terrainWidth = heightMap.Width;
            terrainHeight = heightMap.Height;

            Color[] heightMapColors = new Color[terrainWidth * terrainHeight];
            heightMap.GetData(heightMapColors);

            heightData = new float[terrainWidth, terrainHeight];
            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainHeight; y++)
                    //Determine height using grayscale
                    //RBG all the same.
                    //Scale height by 5.0
                    heightData[x, y] = heightMapColors[x + y * terrainWidth].R / 5.0f;
        }

        //Generate a 2D Color array to represent a picture.
        private Color[,] MakeColorArray(Texture2D picture)
        {

            //Turn texture into 1D array
            Color[] pictureColors = new Color[picture.Width * picture.Height];
            picture.GetData(pictureColors);

            //Iterate through picture array and populate it.
            Color[,] pictureArray = new Color[picture.Width, picture.Height];
            for (int x = 0; x < picture.Width; x++)
            {
                for (int y = 0; y < picture.Height; y++)
                {
                    //Determine height using grayscale
                    //RBG all the same.
                    //x + z * terrainWidth
                    pictureArray[x, y] = pictureColors[x*terrainWidth + y];
                }
            }

            //Return generated array.
            return pictureArray;
        }

        private void CalculateNormals()
        {
            //Initalize all normals to zero
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal = new Vector3(0, 0, 0);

            //Iterate through every triangle
            for (int i = 0; i < indices.Length / 3; i++)
            {
                //Get the indice of every triangle.
                //Cast any negative indice into a positive one.
                int index1 = (ushort)indices[i * 3];
                int index2 = (ushort)indices[i * 3 + 1];
                int index3 = (ushort)indices[i * 3 + 2];


                //Calculate vector representing two of the sides of the triangle.
                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                //Calculate the cross product between sides.
                Vector3 normal = Vector3.Cross(side1, side2);

                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }

            //Normalize all vertices.
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();
        }

        /**
         * Return a bounding box based on a model.
         */
        public BoundingBox CalculateBoundingBox(Model model, Vector3 dimensionScale)
        {

            // Create variables to hold min and max xyz values for the model. Initialise them to extremes
            Vector3 modelMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            Vector3 modelMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

            foreach (ModelMesh mesh in model.Meshes)
            {
                //Create variables to hold min and max xyz values for the mesh. Initialise them to extremes
                Vector3 meshMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
                Vector3 meshMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

                // There may be multiple parts in a mesh (different materials etc.) so loop through each
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    // The stride is how big, in bytes, one vertex is in the vertex buffer
                    // We have to use this as we do not know the make up of the vertex
                    int stride = part.VertexBuffer.VertexDeclaration.VertexStride;

                    byte[] vertexData = new byte[stride * part.NumVertices];
                    part.VertexBuffer.GetData(part.VertexOffset * stride, vertexData, 0, part.NumVertices, 1); // fixed 13/4/11

                    // Find minimum and maximum xyz values for this mesh part
                    // We know the position will always be the first 3 float values of the vertex data
                    Vector3 vertPosition = new Vector3();
                    for (int ndx = 0; ndx < vertexData.Length; ndx += stride)
                    {
                        vertPosition.X = BitConverter.ToSingle(vertexData, ndx);
                        vertPosition.Y = BitConverter.ToSingle(vertexData, ndx + sizeof(float));
                        vertPosition.Z = BitConverter.ToSingle(vertexData, ndx + sizeof(float) * 2);

                        // update our running values from this vertex
                        meshMin = Vector3.Min(meshMin, vertPosition);
                        meshMax = Vector3.Max(meshMax, vertPosition);
                    }
                }

                Matrix[] m_transforms = new Matrix[model.Bones.Count];
                model.CopyAbsoluteBoneTransformsTo(m_transforms);
                // transform by mesh bone transforms
                meshMin = Vector3.Transform(meshMin, m_transforms[mesh.ParentBone.Index]);
                meshMax = Vector3.Transform(meshMax, m_transforms[mesh.ParentBone.Index]);

                // Expand model extents by the ones from this mesh
                modelMin = Vector3.Min(modelMin, meshMin);
                modelMax = Vector3.Max(modelMax, meshMax);
            }

            //Scale the maximum and minimum box.
            Vector3 scaledMin = modelMin * dimensionScale;
            Vector3 scaledMax = modelMax * dimensionScale;


            // Create and return the model bounding box
            return new BoundingBox(scaledMin, scaledMax);

        }


        private void SetUpCamera()
        {
            camera = new Camera(device);
            camera.translatePosition(boatPosition.X, boatPosition.Y + 34.5f, boatPosition.Z-70);
            camera.translateTarget(boatPosition.X, boatPosition.Y + 29.5f, boatPosition.Z);
            //viewMatrix = Matrix.CreateLookAt(new Vector3(60, 100, 100), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            //projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 1.0f, 300.0f);
        }

        protected void updateTitle(GameTime gameTime)
        {
            // Get the keyboard/gamepad states
            KeyboardState keyState = Keyboard.GetState();
            GamePadState gameState = GamePad.GetState(PlayerIndex.One);

            ////check to see if the enter key is up
            //if (keyState.IsKeyUp(Keys.Enter) &&
            //        gameState.IsButtonUp(Buttons.Start))
            //{
            //    bEnterUp = true;
            //}

            //maker sure the enter key isn't being pressed already
           // if (bEnterUp)
           // {
                // Check if the player has decided to begin the game
                if (keyState.IsKeyDown(Keys.Enter) ||
                    gameState.IsButtonDown(Buttons.Start))
                {
                    //bEnterUp = false;
                    //goToHelpScreen();
                    iGameState = 1;
                }
            //}
        }

        protected override void Update(GameTime gameTime)
        {
            if (iGameState == 0)
            {
                //if the gamestate is 0 we want the title screen to update
                updateTitle(gameTime);
            }
            else if (iGameState == 1)
            {
                UpdateGame(gameTime);
            }

            base.Update(gameTime);
        }

        protected void UpdateGame(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            if (passedFinish)
            {
                camera.Position = Vector3.Lerp(camera.Position, finishCamPos, 0.5f);
                boatPosition.Z -= 10.0f;
                camera.translateTarget(0, 0, -10.0f);
                camera.Update();
                return;
            }

            if (boatPosition.Z <= finishZ && !passedFinish)
            {
                passedFinish = true;
                finishCamPos.X = boatPosition.X;
                camera.UpDirection = Vector3.Up;
            }

            if (sinking)
            {
                sinkTimerCount++;
                boatPosition.Y -= 0.1f;
                if (sinkTimerCount >= sinkTimerDelay)
                {
                    sinking = false;
                    sinkTimerCount = 0;
                    boatPosition = new Vector3(10.0f, 0f, Math.Min(boatPosition.Z+100,1000));
                    camera = new Camera(device);
                    camera.translatePosition(boatPosition.X, boatPosition.Y + 34.5f, boatPosition.Z - 70);
                    camera.translateTarget(boatPosition.X, boatPosition.Y + 29.5f, boatPosition.Z);
                }

                // Update nothing else while boat is sinking.
                return;
            }

            // TODO: Add your update logic here
            //Update game time
            decimal elapsed = (decimal)gameTime.ElapsedGameTime.TotalSeconds;
            iTime += elapsed;


            KeyboardState keyState = Keyboard.GetState();

            // Lean left/right when player presses arrow key
            if (keyState.IsKeyDown(Keys.Left))
            {
                if (boatRotation <= 0)
                    boatRotation = Math.Max(boatRotation - 1.0f, -maxAngle);
                else
                    boatRotation = Math.Max(boatRotation - 2.0f, -maxAngle);
            }
            else if (keyState.IsKeyDown(Keys.Right))
            {
                if (boatRotation >= 0)
                    boatRotation = Math.Min(boatRotation + 1.0f, maxAngle);
                else
                    boatRotation = Math.Min(boatRotation + 2.0f, maxAngle);
            }
            else
            {
                // Return to an upright position if no keys are pressed
                if (Math.Abs(boatRotation) < 1.5) boatRotation = 0;
                else boatRotation -= Math.Sign(boatRotation) * 1.5f;
            }

            // Accelerate the boat if it's not already at max speed.
            if (boatSpeed < boatMaxSpeed)
            {
                boatSpeed = Math.Min(boatSpeed + boatAccel, boatMaxSpeed);
            }
            else if (boatSpeed > boatMaxSpeed) // could occur due to multiple collisions
            {
                boatSpeed = Math.Max(boatSpeed - boatAccel, boatMaxSpeed);
            }

            // Clamp the speed to its absolute max.
            boatSpeed = MathHelper.Clamp(boatSpeed, -boatAbsMaxSpeed, boatAbsMaxSpeed);

            Vector3 targetUp = new Vector3((float)Math.Sin(MathHelper.ToRadians(boatRotation)),
                                           (float)Math.Cos(MathHelper.ToRadians(boatRotation)), 0.0f);
            camera.UpDirection = Vector3.Lerp(camera.UpDirection, targetUp, 0.1f);

            camera.translatePosition(boatRotation / 45 * cameraSwing, 0, -boatSpeed);
            camera.translateTarget(boatRotation / 45, 0, -boatSpeed);
            boatPosition.X += boatRotation / 45;
            boatPosition.Z += -boatSpeed;

			//TODO: Disable buoy code.
            // TEST: testing buoyCollision method
            if (!testKnockbackPressed && keyState.IsKeyDown(Keys.B))
            {
                testKnockbackPressed = true;
                buoyCollision(buoyPositions[0]);
            }
            else if (!keyState.IsKeyDown(Keys.B))
            {
                testKnockbackPressed = false;
            }

            // Have the buoys bob up and down.
            bobOffset = (bobOffset + bobSpeed) % MathHelper.TwoPi;
            for (int i = 0; i < buoyPositions.Count; i++)
            {
                float bobPosition = (float)(Math.Cos(bobOffset + buoyPositions[i].Z / waveSpeed) * bobHeight);
                buoyPositions[i] = new Vector3(buoyPositions[i].X, -45 + bobPosition, buoyPositions[i].Z);
            }

            //Handle collisions
            handleCollisions();

            camera.Update();

            base.Update(gameTime);
        }

        protected void handleCollisions()
        {
            //Generate a boundingSphere for the boat.
            BoundingSphere boatSphere = new BoundingSphere(boatPosition, 20);

            //Check if boat has hit the river bank.
            for (int i = 0; i < riverEdge.Length; i++)
            {
                //Boat has hit river bank.
                if(boatSphere.Contains(riverEdge[i]) == ContainmentType.Intersects)
                {
                    riverCollision();
                }
            }

            //Check if we hit the center of a buoy.
            for (int i = 0; i < buoyPositions.Count; i++)
            {
                //Apply offset to counter improperly centered model.
                if (boatSphere.Contains(new BoundingSphere(buoyPositions[i] + new Vector3(95, 60, 25), 5)) == ContainmentType.Intersects)
                {
                    buoyCollision(buoyPositions[i]);
                    break;
                }
            }
        }

        /// <summary>
        /// Call this method when a collision has been detected.
        /// </summary>
        /// <param name="buoyHit">The buoy that the boat collided with.</param>
        private void buoyCollision(Vector3 buoyHit)
        {
            // First, find and remove the buoy that was struck.
            buoyPositions.Remove(buoyHit);

            // Knock the boat backwards.
            boatSpeed *= -knockback;
        }

        private void riverCollision()
        {
            sinking = true;
            sinkTimerCount = 0;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            device.Clear(Color.Black);
            //begin the spriteBatch
            spriteBatch.Begin();

            if (iGameState == 0)
            {
                // Just draw the title screen
                spriteBatch.Draw(titleScreen, new Rectangle(0, 0, 500, 500), Color.White);
                
            }

            else
            {
                RasterizerState rs = new RasterizerState();
                rs.CullMode = CullMode.None;
                //rs.FillMode = FillMode.WireFrame;
                rs.FillMode = FillMode.Solid;
                device.RasterizerState = rs;


                Matrix worldMatrix = Matrix.CreateTranslation(-terrainWidth / 2.0f * terrainScale.X, 0, terrainHeight / 2.0f * terrainScale.Z);

                //effect.CurrentTechnique = effect.Techniques["ColoredNoShading"];
                effect.Parameters["xView"].SetValue(camera.viewMatrix);
                effect.Parameters["xProjection"].SetValue(camera.projectionMatrix);
                effect.Parameters["xWorld"].SetValue(worldMatrix);

                //Light drawing settings.
                effect.CurrentTechnique = effect.Techniques["Colored"];
                Vector3 lightDirection = new Vector3(1.0f, -1.0f, -1.0f);
                lightDirection.Normalize();
                effect.Parameters["xLightDirection"].SetValue(lightDirection);
                effect.Parameters["xAmbient"].SetValue(0.1f); effect.Parameters["xEnableLighting"].SetValue(true);

                DrawSkybox();

                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();

                    for (int i = 0; i < (terrainHeight - 1) * (terrainWidth - 1) * 2 / 60000; i++)
                    {
                        device.DrawUserIndexedPrimitives(
                            PrimitiveType.TriangleList,
                            vertices,
                            i,
                            vertices.Length,
                            indices,
                            i * 60000,
                            60000,
                            VertexPositionColorNormal.VertexDeclaration);


                    }

                    //Calculate triangle information for debugging.
                    int trianglecount = (terrainHeight - 1) * (terrainWidth - 1) * 2;
                    int modValue = (terrainHeight - 1) * (terrainWidth - 1) * 2 % 60000;
                    int divValue = (terrainHeight - 1) * (terrainWidth - 1) * 2 / 60000;

                    int leftOverTriangles = trianglecount % 60000;

                    //Draw remainder triangles if they exists.
                    if (((terrainHeight - 1) * (terrainWidth - 1) * 2 / 60000) % 60000 != 0 &&
                        ((terrainHeight - 1) * (terrainWidth - 1) * 2) / 60000 != 0)
                    {
                        device.DrawUserIndexedPrimitives(
                            PrimitiveType.TriangleList,
                            vertices,
                            0,
                            vertices.Length,
                        indices,
                        indices.Length - leftOverTriangles * 3,
                        leftOverTriangles,
                        VertexPositionColorNormal.VertexDeclaration);
                    }
                }

                // Draw models
                //Matrix modelMatrix = Matrix.Identity * Matrix.CreateScale(0.01f)
                //*Matrix.CreateRotationX(-MathHelper.PiOver2)
                //* Matrix.CreateRotationZ(MathHelper.ToRadians(-targetRot))
                //* Matrix.CreateTranslation(targetPosition);
                ModelHelper.resetWorldMatrices(buoyPositions.Count);
                // Boat first.
                ModelHelper.boat_WorldMatrix = ModelHelper.ScaleMatrix(ModelHelper.boat_WorldMatrix, 0.02f, 0.02f, 0.02f);
                ModelHelper.boat_WorldMatrix = ModelHelper.RotateMatrix(ModelHelper.boat_WorldMatrix, -90, 180, -boatRotation);
                ModelHelper.boat_WorldMatrix = ModelHelper.TranslateMatrix(ModelHelper.boat_WorldMatrix, boatPosition.X, boatPosition.Y, boatPosition.Z);

                // Now put the buoys in position.
                for (int i = 0; i < buoyPositions.Count; i++)
                {
                    ModelHelper.buoy_WorldMatrix[i] = ModelHelper.ScaleMatrix(ModelHelper.buoy_WorldMatrix[i], 0.15f, 0.15f, 0.15f);
                    ModelHelper.buoy_WorldMatrix[i] = ModelHelper.TranslateMatrix(ModelHelper.buoy_WorldMatrix[i],
                                                                                  buoyPositions[i].X,
                                                                                  buoyPositions[i].Y,
                                                                                  buoyPositions[i].Z);
                }

                ModelHelper.drawModels(camera);

                //Draw a rectangle for the timer
                spriteBatch.Draw(stopWatch, new Rectangle(305, 10, 300, 25), Color.Black);

                //Display only the first two decimal places
                String time = String.Format("{0:0.00}", iTime);
                //Draw player score
                spriteBatch.DrawString(aklatanic, "Time Elapsed: " + time, new Vector2(305, 10), Color.CornflowerBlue);

                //Draw debugging shapes if enabled.
                if (bDebugDrawing == true)
                {
                    for (int i = 0; i < riverEdge.Length; i++)
                    {
                        DebugShapeRenderer.AddBoundingBox(riverEdge[i], Color.Violet);
                    }

                    for (int i = 0; i < buoyPositions.Count; i++)
                    {
                        //Apply offset to center to better fit buoy.
                        DebugShapeRenderer.AddBoundingSphere(new BoundingSphere(buoyPositions[i] + new Vector3(95, 60, 25), 5), Color.White);
                    }

                    DebugShapeRenderer.AddBoundingSphere(new BoundingSphere(boatPosition, 20f), Color.YellowGreen);
                    DebugShapeRenderer.Draw(gameTime, camera.viewMatrix, camera.projectionMatrix);
                }
            }

            //End the spriteBatch
            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void DrawSkybox()
        {
            SamplerState ss = new SamplerState();
            ss.AddressU = TextureAddressMode.Clamp;
            ss.AddressV = TextureAddressMode.Clamp;
            device.SamplerStates[0] = ss;

            DepthStencilState dss = new DepthStencilState();
            dss.DepthBufferEnable = false;
            device.DepthStencilState = dss;

            Matrix[] skyboxTransforms = new Matrix[skyboxModel.Bones.Count];
            skyboxModel.CopyAbsoluteBoneTransformsTo(skyboxTransforms);
            int i = 0;

            //tweak the position of the skybox based on where the boat is
            skyPosition = camera.Position;
            //make it look nice in a good place translated from the boat
            skyPosition.Y -= 13;
            skyPosition.Z -= 7;

            foreach (ModelMesh mesh in skyboxModel.Meshes)
            {
                foreach (Effect currentEffect in mesh.Effects)
                {
                    Matrix worldMatrix = skyboxTransforms[mesh.ParentBone.Index] * Matrix.CreateTranslation(skyPosition);
                    currentEffect.CurrentTechnique = currentEffect.Techniques["Textured"];
                    currentEffect.Parameters["xWorld"].SetValue(worldMatrix);
                    currentEffect.Parameters["xView"].SetValue(camera.viewMatrix);
                    currentEffect.Parameters["xProjection"].SetValue(camera.projectionMatrix);
                    currentEffect.Parameters["xTexture"].SetValue(skyboxTextures[i++]);
                }
                mesh.Draw();
            }

            dss = new DepthStencilState();
            dss.DepthBufferEnable = true;
            device.DepthStencilState = dss;
        }
    }
}
